/*
 *    Copyright 2022 The DSMS Authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.dsms.modules.storagedir.service.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dsms.common.constant.ResultCode;
import com.dsms.common.constant.StepTypeEnum;
import com.dsms.common.constant.TaskStatusEnum;
import com.dsms.common.constant.TaskTypeEnum;
import com.dsms.common.exception.DsmsEngineException;
import com.dsms.common.model.PageParam;
import com.dsms.common.taskmanager.TaskContext;
import com.dsms.common.taskmanager.TaskException;
import com.dsms.common.taskmanager.model.AsyncStep;
import com.dsms.common.taskmanager.model.AsyncTask;
import com.dsms.common.taskmanager.model.Step;
import com.dsms.common.taskmanager.model.Task;
import com.dsms.common.taskmanager.service.IStepService;
import com.dsms.common.taskmanager.service.ITaskService;
import com.dsms.common.util.FilterUtil;
import com.dsms.common.util.PageUtil;
import com.dsms.common.validator.DsmsValidateGroup;
import com.dsms.dfsbroker.auth.api.dto.AuthApi;
import com.dsms.dfsbroker.auth.model.AuthDTO;
import com.dsms.dfsbroker.filesystem.FileSystem;
import com.dsms.dfsbroker.filesystem.service.IFileSystemService;
import com.dsms.dfsbroker.storagedir.api.StorageDirApi;
import com.dsms.dfsbroker.storagedir.model.StorageDir;
import com.dsms.dfsbroker.storagedir.model.dto.StorageDirDTO;
import com.dsms.dfsbroker.storagedir.model.remote.SubvolumeAuthListResponse;
import com.dsms.dfsbroker.storagedir.model.remote.SubvolumeInfoResponse;
import com.dsms.dfsbroker.storagedir.model.remote.SubvolumeLsResponse;
import com.dsms.dfsbroker.storagedir.service.IStorageDirService;
import com.dsms.modules.storagedir.model.vo.StorageDirPageVO;
import com.dsms.modules.util.RemoteCallUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;

import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.dsms.common.constant.SystemConst.DSMS_STORAGE_AUTH_PREFIX;

@Service
@Slf4j
@Validated
public class StorgaeDirServiceImpl implements IStorageDirService {

    @Autowired
    private StorageDirApi storageDirApi;
    @Autowired
    private AuthApi authApi;

    @Autowired
    private IFileSystemService fileSystemService;

    @Autowired
    private TaskContext taskContext;

    @Autowired
    private ITaskService taskService;

    @Autowired
    private IStepService stepService;

    @Override
    public List<StorageDir> list() {
        List<StorageDir> storageDirs = new ArrayList<>();
        List<FileSystem> fileSystems = fileSystemService.list();
        if (fileSystems.isEmpty()) {
            return Collections.emptyList();
        }
        try {
            for (FileSystem fileSystem : fileSystems) {
                StorageDirDTO.StorageDirDTOBuilder storageDirDTOBuilder = StorageDirDTO.builder();
                storageDirDTOBuilder.volName(fileSystem.getFsName());
                List<SubvolumeLsResponse> storageDirList = storageDirApi.getStorageDirList(RemoteCallUtil.generateRemoteRequest(), storageDirDTOBuilder.build());
                for (SubvolumeLsResponse subvolumeLsResponse : storageDirList) {
                    storageDirDTOBuilder.subName(subvolumeLsResponse.getName());
                    SubvolumeInfoResponse storageDirInfo = storageDirApi.getStorageDirInfo(RemoteCallUtil.generateRemoteRequest(), storageDirDTOBuilder.build());
                    StorageDir storageDir = StorageDir.subvolumeInfoResponseParseStorageDir(storageDirInfo);
                    storageDir.setFsName(fileSystem.getFsName());
                    storageDir.setStorageDirName(subvolumeLsResponse.getName());
                    storageDir.setStorageDirTotalSize(fileSystem.getDataPoolTotalSize());
                    getStorageDirAuthInfo(storageDirDTOBuilder.build(), storageDir);
                    storageDirs.add(storageDir);
                }
            }
        } catch (Throwable e) {
            log.error("get storage dir info from dsms-storage fail,fail message:{}", e.getMessage(), e);
            throw DsmsEngineException.exceptionWithThrowable(e, ResultCode.STORAGE_DIR_LIST_ERROR);
        }

        return storageDirs;
    }

    @Override
    public Page<StorageDir> page(PageParam pageParam) {
        List<StorageDir> list = list();
        if (pageParam instanceof StorageDirPageVO) {
            StorageDirPageVO nodePageVo = (StorageDirPageVO) pageParam;
            list = list.stream()
                    .filter(node -> FilterUtil.fuzzy(nodePageVo.getStorageDirName(), node.getStorageDirName()))
                    .collect(Collectors.toList());
        }
        return PageUtil.getPageData(list, pageParam.getPageNo(), pageParam.getPageSize());
    }

    @Override
    @Validated(DsmsValidateGroup.Create.class)
    @Transactional(rollbackFor = DsmsEngineException.class)
    public boolean createStorageDir(@Valid StorageDirDTO storageDirDTO) {
        //Check if there is a storage directory with the same name
        List<StorageDir> list = list();
        boolean exists = list.stream().anyMatch(dir -> Objects.equals(dir.getStorageDirName(), storageDirDTO.getSubName()));
        if (exists) {
            throw new DsmsEngineException(ResultCode.STORAGE_DIR_ALREADY_EXIST);
        }

        //1. Verify that the create storage dir task exists
        String taskMessage = TaskTypeEnum.CREATE_STORAGE_DIR.getName() + ":" + storageDirDTO.getVolName() + ", 存储目录:" + storageDirDTO.getSubName();
        if (!taskContext.validateTask(TaskTypeEnum.CREATE_STORAGE_DIR, taskMessage)) {
            log.warn("Task already exist,task message:{}", taskMessage);
            throw new DsmsEngineException(ResultCode.STORAGE_DIR_CREATE_TASK_EXIST);
        }
        Task createStorageDir = new AsyncTask(
                TaskTypeEnum.CREATE_STORAGE_DIR.getName(),
                TaskTypeEnum.CREATE_STORAGE_DIR.getType(),
                TaskStatusEnum.QUEUE.getStatus(),
                taskMessage,
                JSON.toJSONString(storageDirDTO));
        try {
            if (taskService.save(createStorageDir)) {
                createStepTask(createStorageDir.getId(), storageDirDTO);
            }
        } catch (Throwable e) {
            log.error("create storage dir task fail,fail message:{},task:{}", e.getMessage(), JSON.toJSONString(createStorageDir), e);
            throw DsmsEngineException.exceptionWithThrowable(e, ResultCode.STORAGE_DIR_CREATE_TASK_ERROR);
        }
        return true;

    }

    @Override
    @Validated(DsmsValidateGroup.Remove.class)
    @Transactional(rollbackFor = DsmsEngineException.class)
    public boolean deleteStorageDir(@Valid StorageDirDTO storageDirDTO) {
        //1. Verify that the delete storage dir task exists
        String taskMessage = TaskTypeEnum.DELETE_STORAGE_DIR.getName() + ":" + storageDirDTO.getVolName() + ", 存储目录:" + storageDirDTO.getSubName();
        if (!taskContext.validateTask(TaskTypeEnum.DELETE_STORAGE_DIR, taskMessage)) {
            log.warn("Task already exist,task message:{}", taskMessage);
            throw new DsmsEngineException(ResultCode.STORAGE_DIR_REMOVE_TASK_EXIST);
        }

        try {
            String storageDirAuthId = getStorageDirAuthId(storageDirDTO);
            storageDirDTO.setAuthId(storageDirAuthId);
            Task deleteStorageDir = new AsyncTask(
                    TaskTypeEnum.DELETE_STORAGE_DIR.getName(),
                    TaskTypeEnum.DELETE_STORAGE_DIR.getType(),
                    TaskStatusEnum.QUEUE.getStatus(),
                    taskMessage,
                    JSON.toJSONString(storageDirDTO));

            if (taskService.save(deleteStorageDir)) {
                deleteStepTask(deleteStorageDir.getId(), storageDirDTO);
            }
        } catch (Throwable e) {
            log.error("delete storage dir task fail,fail message:{},task info:{}", e.getMessage(), JSON.toJSONString(taskMessage), e);
            throw DsmsEngineException.exceptionWithThrowable(e, ResultCode.STORAGE_DIR_REMOVE_TASK_ERROR);
        }

        return true;
    }

    private void getStorageDirAuthInfo(StorageDirDTO storageDirDTO, StorageDir storageDir) throws Throwable {
        String authId = getStorageDirAuthId(storageDirDTO);
        String authKey = "";
        if (StringUtils.hasText(authId)) {
            authKey = authApi.getAuthKey(RemoteCallUtil.generateRemoteRequest(), new AuthDTO(DSMS_STORAGE_AUTH_PREFIX + authId)).getKey();
        }
        storageDir.setStorageDirAuthId(authId);
        storageDir.setStorageDirAuthKey(authKey);
    }

    private String getStorageDirAuthId(StorageDirDTO storageDirDTOBuilder) throws Throwable {
        List<SubvolumeAuthListResponse> storageDirAuthList = storageDirApi.getStorageDirAuthList(RemoteCallUtil.generateRemoteRequest(), storageDirDTOBuilder);
        String authId = "";
        if (!storageDirAuthList.isEmpty()) {
            authId = storageDirAuthList.get(0).getAuthId();
        }
        return authId;
    }

    private boolean deleteStepTask(int taskId, StorageDirDTO storageDirDTO) {
        //1.deauth storage dir step
        Step deauthStorageDir = new AsyncStep(
                taskId,
                StepTypeEnum.DEAUTH_STORAGE_DIR.getName(),
                StepTypeEnum.DEAUTH_STORAGE_DIR.getType(),
                StepTypeEnum.DEAUTH_STORAGE_DIR.getName() + ":" + storageDirDTO.getSubName(),
                TaskStatusEnum.QUEUE.getStatus(),
                JSON.toJSONString(storageDirDTO));

        if (!stepService.save(deauthStorageDir)) {
            throw new TaskException("the task for deauth storage dir step failed");
        }

        //2.delete storage dir step
        Step deleteStorageDir = new AsyncStep(
                taskId,
                StepTypeEnum.DELETE_STORAGE_DIR.getName(),
                StepTypeEnum.DELETE_STORAGE_DIR.getType(),
                StepTypeEnum.DELETE_STORAGE_DIR.getName() + ":" + storageDirDTO.getSubName(),
                TaskStatusEnum.QUEUE.getStatus(),
                JSON.toJSONString(storageDirDTO));

        if (!stepService.save(deleteStorageDir)) {
            throw new TaskException("the task for delete storage dir step failed");
        }

        return true;
    }

    private boolean createStepTask(int taskId, StorageDirDTO storageDirDTO) {
        //1.create storage dir step
        Step createStorageDir = new AsyncStep(
                taskId,
                StepTypeEnum.CREATE_STORAGE_DIR.getName(),
                StepTypeEnum.CREATE_STORAGE_DIR.getType(),
                StepTypeEnum.CREATE_STORAGE_DIR.getName() + ":" + storageDirDTO.getSubName(),
                TaskStatusEnum.QUEUE.getStatus(),
                JSON.toJSONString(storageDirDTO));

        if (!stepService.save(createStorageDir)) {
            throw new TaskException("the task for creating storage dir step failed");
        }
        //2.auth storage dir step
        Step authStorageDir = new AsyncStep(
                taskId,
                StepTypeEnum.AUTH_STORAGE_DIR.getName(),
                StepTypeEnum.AUTH_STORAGE_DIR.getType(),
                StepTypeEnum.AUTH_STORAGE_DIR.getName() + ":" + storageDirDTO.getAuthId(),
                TaskStatusEnum.QUEUE.getStatus(),
                JSON.toJSONString(storageDirDTO));

        if (!stepService.save(authStorageDir)) {
            throw new TaskException("the task for auth storage dir step failed");
        }
        return true;
    }

}
